package cloud.pangui.core.mongo.utils;

import cloud.pangui.core.mongo.element.*;
import com.mongodb.MongoException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 解析sql ，转换为mongodb的数据格式
 *
 * @Auther hepangui
 * @Date 2020/10/06 0001
*/
public class SqlEleParseUtil {

	/**
	 * 检查这是不是一个简单的表达式
	 * 此表达式应是单一的  而不是一个元素集合，也不是一个where的表达式
	 * where表达式的话，应是左右拆分后校验
	 *
	 * @param str
	 * @return
	 */
	public static boolean isSimpleEle(String str) {
		boolean inStr = false;
		for (int i = 0; i < str.length(); i++) {

			char c = str.charAt(i);
			if (c == '\\') {//转义符，取后一位
				c = str.charAt(i + 1);
				i = i + 1;
			}
			if (c == '\'') {
				inStr = !inStr;
				continue;
			}
			// a='a+b'  在字符参数中的符号应忽略
			if (!inStr) {
				if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')' || c == '%') {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * 聚合函数集合
	 */
	private static Set aggrSet;

	/**
	 * 聚合函数匹配正则
	 *
	 * @return
	 */
	public static boolean isAggrFunc(String name) {
		if (aggrSet == null) {
			String[] funcs = new String[]{"max", "min", "avg", "first", "last", "sum", "count"};
			aggrSet = Arrays.stream(funcs).collect(Collectors.toSet());
		}
		return aggrSet.contains(name);
	}

	/**
	 * 解析一堆元素
	 *
	 * @param split
	 * @return
	 */
	public static Ele[] parseEles(String[] split) {
		Ele[] eles = new Ele[split.length];
		for (int i = 0; i < split.length; i++) {
			eles[i] = parseEle(split[i]);
		}
		return eles;
	}

	/**
	 * 解析一个元素
	 *
	 * @param s
	 * @return
	 */
	public static Ele parseEle(String s) {

		s = s.trim();
		// 普通属性，无函数,mongoTemplate
		if (isSimpleEle(s)) {
			return parseSimpleEle(s);
		}
		// 替换加减乘除模运算为函数符,替换之后必然存在括号
		s = replaceSymbolToFunc(s);

		// 替换之后，这个元素必然是一个整体函数，但可能是嵌套的
		int start = s.indexOf("(");
		int end = s.lastIndexOf(")");
		String alias = null;
		if (end < s.length() - 1) {
			alias = s.substring(end + 1).replace(" as ", " ").replace(" AS ", " ").trim();
		}
		String inner = s.substring(start + 1, end);
		String funcName = s.substring(0, start);
		Ele ele;
		if (isAggrFunc(funcName)) {
			// 聚合函数, 聚合函数仅支持一个参数，所以此处不对内部进行拆分
			ele = Aggr.ofSymbol(funcName, parseEle(inner));
		} else {
			// 其他函数，参数多，需要进行拆分
			List<String> splitedEles = splitEleString(inner);
			ele = Func.ofName(funcName, parseEles(splitedEles.toArray(new String[splitedEles.size()])));
		}
		if (alias != null) {
			ele.as(alias);
		}
		return ele;
	}

	/**
	 * 处理简单的元素，没有函数，没有运算
	 *
	 * @param s
	 * @return
	 */
	public static Ele parseSimpleEle(String s) {
		String[] s1;
		if (s.startsWith("'")) {
			int i = s.lastIndexOf("'");
			if (i <= 0) {
				throw new MongoException("元素不完整：" + s);
			}
			if (i == s.length() - 1) {
				s1 = new String[]{s};
			} else {
				String substring = s.substring(0, i + 1);
				String trim = s.substring(i + 1).trim();
				s1 = new String[]{substring, trim};
			}
		} else {
			s1 = s.split("\\s");
		}


		Ele e;
		if (s1[0].startsWith("'") && s1[0].endsWith("'")) {
			e = Value.of(s1[0].substring(1, s1[0].length() - 1));
		} else if (isIntString(s1[0])) {
			try {
				e = Value.of(toInt(s1[0]));
			} catch (Exception ex) {
				e = Value.of(toLong(s1[0]));
			}
		} else if (isNumberString(s1[0])) {
			e = Value.of(toDouble(s1[0]));
		} else {
			e = Field.of(s1[0]);
		}
		if (s1.length > 1) {
			return e.as(s1[s1.length - 1]);
		}
		return e;
	}


	/**
	 * 函数内部函数的拆分
	 * 如mul(add(a，b),c),内部函数为add(a，b),c 需要解析两个表达式，但有不能简单根据逗号分隔
	 * 需要解析括号的位置进行处理
	 *
	 * @param inner
	 * @return
	 */
	public static List<String> splitEleString(String inner) {
		inner = inner.trim();
		int sIndex = inner.indexOf("(");
		if (sIndex == -1) {
			// 内部无嵌套函数
			String[] split = inner.split(",");
			return Arrays.asList(split);
		}

		List<String> funcStrs = new ArrayList<>();
		// 子表达式：   a，b，mul(add(c,sub(e,f)),d);
		// 先找括号左边的表达式，左边的表达式可能是逗号分隔的多个表达式
		int begin = 0;
		for (int i = 0; i < sIndex; i++) {
			char c = inner.charAt(i);
			if (c == '\\') {//转义符，取后一位
				c = inner.charAt(i + 1);
				i = i + 1;
			}
			if (c == ',') {
				funcStrs.add(inner.substring(begin, i));
				begin = i + 1;
			}
		}
		int backets = 0;
		// 从括号处开始，找寻他的结束括号
		for (int i = sIndex; i < inner.length(); i++) {
			char c = inner.charAt(i);
			if (c == '\\') {//转义符，取后一位
				c = inner.charAt(i + 1);
				i = i + 1;
			}
			if (c == '(') {
				backets++;
			}
			if (c == ')') {
				backets--;
				// 结束括号
				if (backets == 0) {
					//此括号后面已经没有逗号，则后面必然没有别的函数
					int dIndex = inner.indexOf(",", i);
					if (dIndex == -1) {
						String func = inner.substring(begin);
						funcStrs.add(func);
						break;
					}
					// 后面仍有更多函数，则截止到逗号之前的，是当前函数
					String func = inner.substring(begin, dIndex);
					funcStrs.add(func);
					// 尚未结束 需要继续解析
					inner = inner.substring(dIndex + 1);
					funcStrs.addAll(splitEleString(inner));
					break;
				}
			}
		}
		if (funcStrs.size() == 0) {
			throw new MongoException("表达式有误，括号不全：" + inner);
		}
		return funcStrs;
	}

	/**
	 * 将加减乘除运算替换为对应的函数表达式
	 *
	 * @param str
	 * @return
	 */
	public static String replaceSymbolToFunc(String str) {
		str = replaceSymbolToFunc(str, '*');
		str = replaceSymbolToFunc(str, '/');
		str = replaceSymbolToFunc(str, '%');
		str = replaceSymbolToFunc(str, '+');
		str = replaceSymbolToFunc(str, '-');
		return str;
	}

	/**
	 * 将对应的符号转换为函数表达式：
	 * 此表达式中不允许有转义符
	 * 实际上，算数运算也用不着转义符
	 * a + b  ->  add(a,b)
	 *
	 * @param eleStr
	 * @param symbol
	 * @return
	 */
	public static String replaceSymbolToFunc(String eleStr, char symbol) {
		int index = getIndexOfSymbol(eleStr, symbol);
		if (index == -1) {
			return eleStr;
		}
		int begin = 0;
		int end = eleStr.length();
		boolean catchNotNull = false;
		int backets = 0;
		// 向左找
		for (int i = index - 1; i >= 0; i--) {
			char c = eleStr.charAt(i);
			if (!catchNotNull && c == ' ') {
				continue;
			} else {
				catchNotNull = true;
			}
			if (catchNotNull && backets == 0 &&
					(c == ' ' || c == ',' || c == '+' || c == '-' || c == '/' || c == '%' || c == '*')) {
				begin = i + 1;
				break;
			}
			if (c == '(') {
				backets--;
			}
			if (backets < 0) {
				begin = i + 1;
				break;
			}
			// 获取到一个右括号
			if (c == ')') {
				backets++;
			}
		}
		// 向右找
		catchNotNull = false;
		backets = 0;
		for (int i = index + 1; i < eleStr.length(); i++) {
			char c = eleStr.charAt(i);
			if (!catchNotNull && c == ' ') {
				continue;
			} else {
				catchNotNull = true;
			}
			if (catchNotNull && backets == 0 &&
					(c == ' ' || c == ',' || c == '+' || c == '-' || c == '/' || c == '%' || c == '*')) {
				end = i;
				break;
			}
			if (c == ')') {
				backets--;
			}
			if (backets < 0) {
				end = i;
				break;
			}
			// 获取到一个右括号
			if (c == '(') {
				backets++;
			}
		}

		String left = eleStr.substring(begin, index).trim();
		if (left.startsWith("(") && left.endsWith(")")) {
			left = left.substring(1, left.length() - 1);
		}
		String right = eleStr.substring(index + 1, end).trim();
		if (right.startsWith("(") && right.endsWith(")")) {
			right = right.substring(1, right.length() - 1);
		}
		String funSymbol = null;
		switch (symbol) {
			case '*':
				funSymbol = "mul";
				break;
			case '/':
				funSymbol = "div";
				break;
			case '%':
				funSymbol = "mod";
				break;
			case '+':
				funSymbol = "add";
				break;
			case '-':
				funSymbol = "sub";
				break;
			default:
				throw new MongoException("符号有误：" + symbol);
		}
		eleStr = eleStr.substring(0, begin) + funSymbol + "(" + left + "," + right + ")" + eleStr.substring(end);
		// 继续寻找下一个，递归直到找不到的时候
		return replaceSymbolToFunc(eleStr, symbol);
	}

	/**
	 * 获取指定符号的index
	 * 跳过‘’内部的字符串
	 *
	 * @param str
	 * @param symbol
	 * @return
	 */
	public static int getIndexOfSymbol(String str, char symbol) {
		boolean inStr = false;
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			if (c == '\\') {//转义符，取后一位
				c = str.charAt(i + 1);
				i = i + 1;
			}
			if (c == '\'') {
				inStr = !inStr;
			}
			// a='a+b'  在字符参数中的符号应忽略
			if (!inStr) {
				if (c == symbol) {
					return i;
				}
			}
		}
		return -1;
	}

	/**
	 * 判断是不是int类型的字符
	 *
	 * @param str
	 * @return
	 */
	public static boolean isIntString(String str) {
		return str.matches("-?\\d+");
	}

	public static boolean isNumberString(String strNum) {
		return strNum.matches("-?\\d+(\\.\\d+)?");
	}

	public static int toInt(String str) {
		try {
			return Integer.parseInt(str);
		} catch (NumberFormatException e) {
			throw MongoException.fromThrowable(e);
		}
	}

	public static long toLong(String str) {
		try {
			return Long.parseLong(str);
		} catch (NumberFormatException e) {
			throw MongoException.fromThrowable(e);
		}
	}

	public static double toDouble(String str) {
		try {
			return Double.parseDouble(str);
		} catch (NumberFormatException e) {
			throw MongoException.fromThrowable(e);
		}
	}
}
